package com.inspur.security.cbb3.kms.keks.service.impl;

import com.inspur.security.cbb3.kms.enums.AlgorithmType;
import com.inspur.security.cbb3.kms.enums.EncryptMode;
import com.inspur.security.cbb3.kms.enums.PaddingMode;
import com.inspur.security.cbb3.kms.exception.UserInfoInVaildException;
import com.inspur.security.cbb3.kms.keks.po.KeksBean;
import com.inspur.security.cbb3.kms.keks.repository.KeksRepository;
import com.inspur.security.cbb3.kms.keks.service.KeksService;
import com.inspur.security.cbb3.kms.keks.vo.CipherTextVo;
import com.inspur.security.cbb3.kms.mkek.Mkek;
import com.inspur.security.cbb3.kms.sysuser.po.SysUserBean;
import com.inspur.security.cbb3.kms.sysuser.repository.SysUserRepository;
import com.inspur.security.cbb3.kms.sysuser.service.SysUserService;
import com.inspur.security.cbb3.kms.utils.DateUtil;
import com.inspur.security.cbb3.kms.utils.KeyUtil;
import com.inspur.security.cbb3.kms.utils.RegExpUtil;
import com.inspur.security.cbb3.kms.utils.parameter.cipher.impl.AESCipherParams;
import com.inspur.security.cbb3.kms.utils.parameter.cipher.impl.DES3CipherParams;
import com.inspur.security.cbb3.kms.utils.parameter.cipher.impl.SM4CipherParams;
import com.inspur.security.cbb3.kms.utils.parameter.keygen.impl.AESKeyGenParams;
import com.inspur.security.cbb3.kms.utils.parameter.keygen.impl.SM4KeyGenParams;
import javassist.NotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.MissingServletRequestParameterException;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.validation.ValidationException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
 * KeksServiceImpl
 *
 * @author liukai
 * @date 2020/10/26
 **/
@Slf4j
@Service
public class KeksServiceImpl implements KeksService {

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private KeksRepository keksRepository;

    @Autowired
    private Mkek mkek;

    @Value("${signweb.kek.RotateCycle}")
    private String rotateCycle="3";

    @Autowired
    private SysUserRepository sysUserRepository;

    @Override
    public KeksBean addKek(KeksBean keksBean) {
        if (!RegExpUtil.checkKekAlgorithm(keksBean.getAlgorithm())) {
            throw new ValidationException("算法类型为空或不支持");
        }
        //根据算法类型生成相应的密钥
        String keyContent = null;
        if ("aes".equalsIgnoreCase(keksBean.getAlgorithm())) {
            if (StringUtils.isEmpty(keksBean.getBitLength())) {
                keksBean.setBitLength("256");
            } else if (!Arrays.asList(AlgorithmType.aes.getBitLength()).contains(keksBean.getBitLength().toLowerCase())) {
                throw new ValidationException("算法长度不支持");
            }
            // 密钥生成
            AESKeyGenParams aesKeyGenParams = new AESKeyGenParams();
            aesKeyGenParams.setKeySize(Integer.parseInt(keksBean.getBitLength()));
            try {
                KeyUtil.genKey(aesKeyGenParams);
            } catch (Exception e) {
                log.error("generate aeskek failed");
                throw new ValidationException("二级密钥生成失败");
            }
            keyContent = aesKeyGenParams.getKeyBase64();
        }
        if ("sm4".equalsIgnoreCase(keksBean.getAlgorithm())) {
            if (StringUtils.isEmpty(keksBean.getBitLength())) {
                keksBean.setBitLength("128");
            } else if (!Arrays.asList(AlgorithmType.aes.getBitLength()).contains(keksBean.getBitLength().toLowerCase())) {
                throw new ValidationException("算法长度不支持");
            }
            // sm4密钥生成
            SM4KeyGenParams sm4KeyGenParams = new SM4KeyGenParams();
//            sm4KeyGenParams.setKeySize(128);
            try {
                KeyUtil.genKey(sm4KeyGenParams);
            } catch (Exception e) {
                log.error("generate sm4kek failed");
                throw new ValidationException("二级密钥生成失败");
            }
            keyContent = sm4KeyGenParams.getKeyBase64();
        }
        //使用一级密钥加密二级密钥
        String encKeyContent = mkek.encrypt(keyContent);
        //保存
        try {
            keksBean.setCreatedTime(DateUtil.getNowDate());
            keksBean.setActive(1);
            keksBean.setPluginName(mkek.getPluginName());
            keksBean.setKeyContent(encKeyContent);
            keksBean.setEncryptMode(EncryptMode.CBC.name());
            keksBean.setPaddingMode(PaddingMode.PKCS5Padding.name());
            //生成随机iv
            SecureRandom s = new SecureRandom();
            byte[] ivBytes = new byte[16];
            s.nextBytes(ivBytes);
            String iv = Hex.toHexString(ivBytes);
            keksBean.setIv(iv);
            keksRepository.save(keksBean);
        } catch (Exception e) {
            throw new RequestRejectedException("Kek生成失败");
        }
        return keksBean;
    }

    @Override
    public String getKekByUuid(String uuid) throws NotFoundException, MissingServletRequestParameterException {

        //权限校验
        KeksBean keksBean = checkPermission(uuid);
        return keksBean.getKeyContent();
    }

    @Override
    public KeksBean getActiveKek(String creatorId) throws NotFoundException {
        SysUserBean loginUser = sysUserService.getByUuid(creatorId);
        List<KeksBean> keksBeanList = keksRepository.findByActiveAndUser(1, loginUser.getUuid());
        //如果keksBeanList为空，重新生成一个kek
        if (CollectionUtils.isEmpty(keksBeanList)) {
            KeksBean keksBean = new KeksBean();
            keksBean.setActive(1);
            keksBean.setAlgorithm("sm4");
            keksBean.setBitLength("128");
            keksBean.setCreatorId(creatorId);
            return addKek(keksBean);
        }
        //同一时间点只能有一个Kek密钥的active值为1，若>1，则取list[0]为默认主密钥，并把其他kek的密钥值改为0
        if (keksBeanList.size() > 1) {
            for (int i = 1; i < keksBeanList.size(); i++) {
                keksRepository.updataActive(0, keksBeanList.get(1).getUuid());
            }
        }
        return keksBeanList.get(0);
    }

    /**
     * 根据用户及明文获取用户二级密钥加密密文
     *
     * @param plainText 明文信息
     * @param creatorId 操作用户uuid
     * @return 加密密文
     */
    @Override
    public CipherTextVo getEncryptString(String plainText, String creatorId) {
        //2.用用户的二级密钥对三级密钥进行加密
        //2.1获取用户的二级密钥
        KeksBean keksBean;
        try {
            keksBean = this.getActiveKek(creatorId);
        } catch (NotFoundException e) {
            log.error("getEncryptString 获取二级密钥失败 ");
            return null;
        }
        // 用一级密钥解密二级秘钥密文，获取明文信息
        String keysCipherText = mkek.decrypt(keksBean.getKeyContent());
        if (StringUtils.isBlank(keysCipherText)) {
            log.error("getEncryptString 一级密钥解密二级秘钥密文失败 ");
            return null;
        }
        //使用二级密钥对明文信息进行加密
        //2.2根据二级密钥的算法调用KeyUtil的对应算法对三级密钥进行加密
        try {
            CipherTextVo cipherTextVo = new CipherTextVo();
            cipherTextVo.setKekId(keksBean.getUuid());
            //获取二级密钥的类型,均为对称加密
            switch (AlgorithmType.getByType(keksBean.getAlgorithm().toLowerCase(Locale.ENGLISH))) {
                case aes:
                    AESCipherParams aesCipherParams = new AESCipherParams();
                    aesCipherParams.setPlaintext(plainText);
                    aesCipherParams.setKeyBase64(keysCipherText);
                    aesCipherParams.setMode(keksBean.getMode());
                    aesCipherParams.setPadding(keksBean.getPaddingMode());
                    KeyUtil.encrypt(aesCipherParams);
                    cipherTextVo.setCipherText(aesCipherParams.getCiphertextBase64());
                    break;
                case desede:
                    DES3CipherParams des3CipherParams = new DES3CipherParams();
                    des3CipherParams.setPlaintext(plainText);
                    des3CipherParams.setKeyBase64(keysCipherText);
                    des3CipherParams.setMode(keksBean.getMode());
                    des3CipherParams.setPadding(keksBean.getPaddingMode());
                    KeyUtil.encrypt(des3CipherParams);
                    cipherTextVo.setCipherText(des3CipherParams.getCiphertextBase64());
                    break;
                case sm4:
                    SM4CipherParams sm4CipherParams = new SM4CipherParams();
                    sm4CipherParams.setPlaintext(plainText);
                    sm4CipherParams.setKeyBase64(keysCipherText);
                    sm4CipherParams.setMode(keksBean.getMode());
                    sm4CipherParams.setPadding(keksBean.getPaddingMode());
                    KeyUtil.encrypt(sm4CipherParams);
                    cipherTextVo.setCipherText(sm4CipherParams.getCiphertextBase64());
                    break;
                default:
                    cipherTextVo=null;
            }
            return cipherTextVo;
        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | InvalidKeySpecException | IOException e) {
            log.error("getEncryptString error", e);
        }
        return null;
    }

    /**
     * 根据用户及密文获取密文解密信息
     *
     * @param cipherText 密文信息
     * @param kekId 二级密钥uuid
     * @return 解密明文
     */
    @Override
    public String getDecryptString(String cipherText, String kekId) {
        //获取用户二级密钥
        Optional<KeksBean> optional =keksRepository.findById(kekId);

        if(!optional.isPresent()){
            log.info("getDecryptString keksBean 为空，传入二级密钥主键：{}",kekId);
            return null;
        }
        KeksBean keksBean=optional.get();
        String ciphertextKek = keksBean.getKeyContent();
        if (StringUtils.isBlank(ciphertextKek)) {
            log.error("getDecryptString 获取二级密钥密文失败");
            return null;
        }
        String plaintextKek = mkek.decrypt(ciphertextKek);
        if (StringUtils.isBlank(plaintextKek)) {
            log.error("getDecryptString 二级密钥密文解密失败");
            return null;
        }
        //用一级密钥解密二级秘钥密文，获取明文信息
        String keysCiphertext = mkek.decrypt(keksBean.getKeyContent());
        //使用二级密钥对密文信息进行解密
        //获取二级密钥的类型,均为对称加密
        try {
            switch (AlgorithmType.getByType(keksBean.getAlgorithm().toLowerCase(Locale.ENGLISH))) {
                case aes:
                    AESCipherParams aesCipherParams = new AESCipherParams();
                    aesCipherParams.setCiphertextBase64(cipherText);
                    aesCipherParams.setKeyBase64(keysCiphertext);
                    aesCipherParams.setMode(keksBean.getMode());
                    aesCipherParams.setPadding(keksBean.getPaddingMode());
                    aesCipherParams.setIV(keksBean.getIv());
                    KeyUtil.decrypt(aesCipherParams);
                    return aesCipherParams.getPlaintext();
                case desede:
                    DES3CipherParams des3CipherParams = new DES3CipherParams();
                    des3CipherParams.setCiphertextBase64(cipherText);
                    des3CipherParams.setKeyBase64(keysCiphertext);
                    des3CipherParams.setMode(keksBean.getMode());
                    des3CipherParams.setPadding(keksBean.getPaddingMode());
                    des3CipherParams.setIV(keksBean.getIv());
                    KeyUtil.decrypt(des3CipherParams);
                    return des3CipherParams.getPlaintext();
                case sm4:
                    SM4CipherParams sm4CipherParams = new SM4CipherParams();
                    sm4CipherParams.setCiphertextBase64(cipherText);
                    sm4CipherParams.setKeyBase64(keysCiphertext);
                    sm4CipherParams.setMode(keksBean.getMode());
                    sm4CipherParams.setPadding(keksBean.getPaddingMode());
                    sm4CipherParams.setIV(keksBean.getIv());
                    KeyUtil.decrypt(sm4CipherParams);
                    return sm4CipherParams.getPlaintext();
                default:
                    return null;
            }
        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | InvalidKeySpecException | IOException e) {
            log.error("getDecryptString error", e);
        }
        return null;
    }

    @Override
    public void updateActiveStatus(int active, String uuid) throws NotFoundException, MissingServletRequestParameterException {
        SysUserBean loginUser = sysUserService.findLoginSysUser();
        if (loginUser == null) {
            throw new UserInfoInVaildException();
        }
        //鉴权
        checkPermission(uuid);
        keksRepository.updataActive(active, uuid);
    }

    /**
     * 二级密钥轮转
     */
    @Override
    public void keksRotate() {
        //配置文件读取轮转周期
        int rotateTime = Integer.parseInt(rotateCycle);
        //遍历每个用户当前的kek的创建时间，加上轮转周期与现在时间对比，超过及新增一个kek，将当前kekactive 改为0
        String nowDate = DateUtil.getNowDate();
        List<SysUserBean> sysUserBeanList = sysUserRepository.findAll();
        for (SysUserBean bean: sysUserBeanList){
            try {
                KeksBean keksBean = getActiveKekForRotate(bean.getUuid());
                String createdTime = keksBean.getCreatedTime();
                int difference = DateUtil.calculateTimeDifferenceBySimpleDateFormat(createdTime, nowDate);
                if (difference>=rotateTime){
                    //新增一个kek
                    KeksBean keksBean1 = new KeksBean();
                    keksBean1.setActive(1);
                    keksBean1.setAlgorithm("sm4");
                    keksBean1.setBitLength("128");
                    keksBean1.setCreatorId(bean.getUuid());
                    addKekForRotate(keksBean1);
                    //更新旧kek状态
                    keksRepository.updataActive(0, keksBean.getUuid());
                }

            } catch (NotFoundException e) {
                log.error("轮转用户不存在: "+e.toString());
            } catch (ParseException e){
                log.error("时间差值计算错误: "+e.toString());
            }
        }

    }

    public KeksBean getActiveKekForRotate(String creatorId) throws NotFoundException {
        List<KeksBean> keksBeanList = keksRepository.findByActiveAndUser(1, creatorId);
        //如果keksBeanList为空，重新生成一个kek
        if (CollectionUtils.isEmpty(keksBeanList)) {
            KeksBean keksBean = new KeksBean();
            keksBean.setActive(1);
            keksBean.setAlgorithm("sm4");
            keksBean.setBitLength("128");
            keksBean.setCreatorId(creatorId);
            return addKekForRotate(keksBean);
        }
        //同一时间点只能有一个Kek密钥的active值为1，若>1，则取list[0]为默认主密钥，并把其他kek的密钥值改为0
        if (keksBeanList.size() > 1) {
            for (int i = 1; i < keksBeanList.size(); i++) {
                keksRepository.updataActive(0, keksBeanList.get(1).getUuid());
            }
        }
        return keksBeanList.get(0);
    }

    public KeksBean addKekForRotate(KeksBean keksBean) {
        //根据算法类型生成相应的密钥
        String keyContent = null;
        if ("aes".equalsIgnoreCase(keksBean.getAlgorithm())) {
            if (StringUtils.isEmpty(keksBean.getBitLength())) {
                keksBean.setBitLength("256");
            } else if (!Arrays.asList(AlgorithmType.aes.getBitLength()).contains(keksBean.getBitLength().toLowerCase())) {
                log.error("aes算法长度不支持 ");
            }
            // 密钥生成
            AESKeyGenParams aesKeyGenParams = new AESKeyGenParams();
            aesKeyGenParams.setKeySize(Integer.parseInt(keksBean.getBitLength()));
            try {
                KeyUtil.genKey(aesKeyGenParams);
            } catch (Exception e) {
                log.error("generate aeskek failed");
            }
            keyContent = aesKeyGenParams.getKeyBase64();
        }
        if ("sm4".equalsIgnoreCase(keksBean.getAlgorithm())) {
            if (StringUtils.isEmpty(keksBean.getBitLength())) {
                keksBean.setBitLength("128");
            } else if (!Arrays.asList(AlgorithmType.aes.getBitLength()).contains(keksBean.getBitLength().toLowerCase())) {
                log.error("sm4算法长度不支持 ");
            }
            // sm4密钥生成
            SM4KeyGenParams sm4KeyGenParams = new SM4KeyGenParams();
            try {
                KeyUtil.genKey(sm4KeyGenParams);
            } catch (Exception e) {
                log.error("generate sm4kek failed");
            }
            keyContent = sm4KeyGenParams.getKeyBase64();
        }
        //使用一级密钥加密二级密钥
        String encKeyContent = mkek.encrypt(keyContent);
        //保存
        try {
            keksBean.setCreatedTime(DateUtil.getNowDate());
            keksBean.setActive(1);
            keksBean.setPluginName(mkek.getPluginName());
            keksBean.setKeyContent(encKeyContent);
            keksBean.setEncryptMode(EncryptMode.CBC.name());
            keksBean.setPaddingMode(PaddingMode.PKCS5Padding.name());
            //生成随机iv
            SecureRandom s = new SecureRandom();
            byte[] ivBytes = new byte[16];
            s.nextBytes(ivBytes);
            String iv = Hex.toHexString(ivBytes);
            keksBean.setIv(iv);
            keksRepository.save(keksBean);
        } catch (Exception e) {
            log.error("generate sm4kek failed");
        }
        return keksBean;
    }

    /**
     * 校验登录用户是否有二级密钥的操作权限
     *
     * @param uuid 二级密钥id
     * @return 数据对象
     * @throws MissingServletRequestParameterException 异常信息-主键参数必填
     * @throws NotFoundException                       异常信息-未找到对象
     */
    private KeksBean checkPermission(String uuid) throws MissingServletRequestParameterException, NotFoundException {
        if (StringUtils.isBlank(uuid)) {
            throw new MissingServletRequestParameterException("主键字段uuid", "String");
        }
        SysUserBean loginUser = sysUserService.findLoginSysUser();
        if (loginUser == null) {
            throw new UserInfoInVaildException();
        }
        Optional<KeksBean> optional = keksRepository.findById(uuid);
        if (!optional.isPresent()) {
            throw new NotFoundException("对象不存在");
        }
        KeksBean bean = optional.get();
        if (!loginUser.getUuid().equalsIgnoreCase(bean.getCreatorId())) {
            throw new NotFoundException("对象不存在");
        }
        return bean;
    }
}
